﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Media.Media3D;
using LFSRecord2.Misc;
using LFSRecord2.Model.Undo;

namespace LFSRecord2.Model
{
    public abstract class CompLayerBase : INotifyPropertyChanged, IDisposable
    {
        public CompLayerMeshBase LayerObjectMesh { get; set; }

        public abstract Matrix3D GetTransformMatrixAtFrame(int frame);
        public abstract void SetMeshLookAtPointAtFrame(int frame);
        public abstract void SetMeshCenterPointAtFrame(int frame);
        public abstract void RemoveParentingJump(CompLayerBase parentLayer);
        public abstract void AddParentingJump();

        public short LayerIndex { get; set; }
        public int UniqueID { get; set; }

        public CompLayerTypes LayerType
        {
            get;
            protected set;
        }

        private string _layerName = "";
        public string LayerName
        {
            get { return (string)_layerName; }
            set
            { 
                _layerName = value;
                OnPropertyChanged("LayerName");
            }
        }

        private CompLayerBase _parentLayer = null;
        public CompLayerBase ParentLayer
        {
            get { return _parentLayer; }
            set
            {
                // Remove parent listener
                if (_parentLayer != null)
                {
                    // Remove old parent's property changed listener
                    _parentLayer.PropertyChanged -= parentLayer_PropertyChanged;

                    // We have to remove the old parent's offsets from ourselves (the 'jump' compensation)
                    //AddParentingJump();
                }

                if (value != null)
                {
                    // Adjust ourselves according to parent to compensate for 'jump'
                    //RemoveParentingJump(value);

                    // Listen to the parent
                    value.PropertyChanged += parentLayer_PropertyChanged;
                }

                _parentLayer = value;

                SetCurrentTransformMatrix();
                UpdatedCurrentValues = CompLayerPropertyUpdateTypes.All;
                DoneUpdatingCurrentValues = !_doneUpdatingCurrentValues;

                OnPropertyChanged("ParentLayer");
                OnPropertyChanged("HasParentLayer");
            }
        }

        void parentLayer_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "DoneUpdatingCurrentValues")
            {
                if (LayerType == CompLayerTypes.LookCamera)
                    SetCurrentTransformMatrix();

                UpdatedCurrentValues = ParentLayer.UpdatedCurrentValues;
                DoneUpdatingCurrentValues = !DoneUpdatingCurrentValues;
            }
        }

        public bool HasParentLayer
        {
            get
            {
                return (_parentLayer != null);
            }
        }

        private bool _isCollapsed = true;
        public bool IsCollapsed
        {
            get { return (bool)_isCollapsed; }
            set
            {
                _isCollapsed = value;
                OnPropertyChanged("IsCollapsed");
            }
        }

        private bool _isSelected = false;
        public bool IsSelected
        {
            get { return (bool)_isSelected; }
            set
            {
                bool oldValue = _isSelected;
                _isSelected = value;
                if (oldValue != _isSelected)
                    OnPropertyChanged("IsSelected");
            }
        }

        private bool _isActiveCamera = false;
        public bool IsActiveCamera
        {
            get { return _isActiveCamera; }
            set
            {
                _isActiveCamera = value;
                OnPropertyChanged("IsActiveCamera");
            }
        }

        private bool _doneUpdatingCurrentValues = false;
        public bool DoneUpdatingCurrentValues
        {
            get { return (bool)_doneUpdatingCurrentValues; }
            set
            {
                _doneUpdatingCurrentValues = value;
                OnPropertyChanged("DoneUpdatingCurrentValues");
            }
        }
        public CompLayerPropertyUpdateTypes UpdatedCurrentValues = 0;

        private Matrix3D _currentLayerMatrix = new Matrix3D();
        public Matrix3D CurrentLayerMatrix
        {
            get { return _currentLayerMatrix; }
            set
            {
                _currentLayerMatrix = value;
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public event EventHandler Disposed;
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (HasParentLayer)
                    ParentLayer = null;

                Properties.Clear();

                EventHandler handler = this.Disposed;
                if (handler != null)
                {
                    var e = new EventArgs();
                    handler(this, e);
                }

                PropertyChanged = null;
                Disposed = null;
            }
        }

        public List<CompLayerPropertyBase> Properties { get; protected set; }

        public bool HasSelectedProperties()
        {
            foreach (CompLayerPropertyBase prop in Properties)
            {
                if (prop.IsSelected)
                    return true;
            }
            return false;
        }

        public int LastKeyFrameNum()
        {
            int index = -1;
            foreach (CompLayerPropertyBase prop in Properties)
            {
                if (prop.KeyFrames.Count > 0 && prop.KeyFrames[prop.KeyFrames.Count - 1].FrameNum > index)
                    index = prop.KeyFrames[prop.KeyFrames.Count - 1].FrameNum;
            }
            return index;
        }

        public void SetCurrentValues(int frame)
        {
            UpdatedCurrentValues = 0;

            CompLayerPropertyBase p;
            for (int a = 0; a < Properties.Count; a++)
            {
                p = Properties[a];
                if (p.HasBoundProperty)
                    continue;

                if (p.SetCurrentValue(frame))
                {
                    if (p.PropertyName == "Position" || p.PropertyName == "Look At" || p.PropertyName == "Anchor")
                        UpdatedCurrentValues |= CompLayerPropertyUpdateTypes.Position;
                    else if (p.PropertyName == "Rotate X" || p.PropertyName == "Rotate Y" || p.PropertyName == "Rotate Z" || p.PropertyName == "Orientation" ||
                             p.PropertyName == "Heading" || p.PropertyName == "Pitch" || p.PropertyName == "Roll")
                        UpdatedCurrentValues |= CompLayerPropertyUpdateTypes.Rotation;
                    else if (p.PropertyName == "Scale")
                        UpdatedCurrentValues |= CompLayerPropertyUpdateTypes.Scale;
                }
            }

            SetCurrentTransformMatrix();

            // We toggle DoneUpdatingCurrentValues dependency property, so that certain listening objects (CompLayerViewObject) know when to redraw
            //DoneUpdatingCurrentValues = !DoneUpdatingCurrentValues;
            //Debug.WriteLine("Done updating current values of " + LayerName);
        }

        public void SetCurrentTransformMatrix()
        {
            //Debug.WriteLine("Setting new CurrentTransformMatrix");
            CurrentLayerMatrix = GetTransformMatrixAtFrame(LfsRecordController.FrameIndex);
        }

        public CompLayerMeshBase CreateTransformedMesh(int frame, Vector3D scale)
        {
            // Prepare our mesh
            SetMeshLookAtPointAtFrame(frame);
            SetMeshCenterPointAtFrame(frame);

            CompLayerMeshBase newMesh = LayerObjectMesh.CreateTransformedMesh(GetParentedTransformMatrixAtFrame(frame), LfsRecordController.ViewTransformMatrix, scale);

            if (LayerType == CompLayerTypes.LookCamera)
            {
                if (HasParentLayer && 
                    Properties[(int)CompLayerLookCamera.PropertyTypes.LookAt].HasBoundProperty && 
                    Properties[(int)CompLayerLookCamera.PropertyTypes.LookAt].BoundPropertyIgnoreParent)
                {
                    // lookat property parent ignore
                    newMesh.Points = (Point3D[])LayerObjectMesh.Points.Clone();
                    Matrix3D newMat = Matrix3D.Multiply(_currentLayerMatrix, LfsRecordController.ViewTransformMatrix);
                    //newMesh.LookAtPoint = (Point3D)Properties[(int)CompLayerLookCamera.PropertyTypes.LookAt].GetValueAtTime(frame);
                    //newMesh.LookAtPoint.X -= ((CompLayerPropertyPoint3D)Properties[(int)CompLayerLookCamera.PropertyTypes.Position]).CurrentXValue;
                    //newMesh.LookAtPoint.Y -= ((CompLayerPropertyPoint3D)Properties[(int)CompLayerLookCamera.PropertyTypes.Position]).CurrentYValue;
                    //newMesh.LookAtPoint.Z -= ((CompLayerPropertyPoint3D)Properties[(int)CompLayerLookCamera.PropertyTypes.Position]).CurrentZValue;
                    
                    newMat.OffsetX -= ((CompLayerPropertyPoint3D)Properties[(int)CompLayerLookCamera.PropertyTypes.Position]).CurrentXValue;
                    newMat.OffsetY -= ((CompLayerPropertyPoint3D)Properties[(int)CompLayerLookCamera.PropertyTypes.Position]).CurrentYValue;
                    newMat.OffsetZ -= ((CompLayerPropertyPoint3D)Properties[(int)CompLayerLookCamera.PropertyTypes.Position]).CurrentZValue;
                    newMat.Scale(scale);
                    newMat.OffsetX = newMesh.CenterPoint.X;
                    newMat.OffsetY = newMesh.CenterPoint.Y;
                    newMat.OffsetZ = newMesh.CenterPoint.Z;
                    newMat.Transform(newMesh.Points);
                    //newMat.Transform(newMesh.LookAtPoint);
                    
                    newMesh.LookAtPoint = LfsRecordController.ViewTransformMatrix.Transform((Point3D)Properties[(int)CompLayerLookCamera.PropertyTypes.LookAt].GetValueAtFrame(frame));
                    newMesh.LookAtPoint = new Point3D(
                        newMesh.LookAtPoint.X - newMesh.CenterPoint.X,
                        newMesh.LookAtPoint.Y - newMesh.CenterPoint.Y,
                        newMesh.LookAtPoint.Z - newMesh.CenterPoint.Z
                        );

                    Point3D orientation = (Point3D)((CompLayerPropertyPoint3D)Properties[(int)CompLayerLookCamera.PropertyTypes.Orientation]).GetValueAtFrame(frame);
                    newMesh.LookAtPoint = MathHelper.RotateY(orientation.Y * MathHelper.DegRadMult, newMesh.LookAtPoint);
                    newMesh.LookAtPoint = MathHelper.RotateZ(orientation.Z * MathHelper.DegRadMult, newMesh.LookAtPoint);
                    newMesh.LookAtPoint = MathHelper.RotateX(orientation.X * MathHelper.DegRadMult, newMesh.LookAtPoint);

                    newMesh.LookAtPoint = new Point3D(
                        newMesh.LookAtPoint.X + newMesh.CenterPoint.X,
                        newMesh.LookAtPoint.Y + newMesh.CenterPoint.Y,
                        newMesh.LookAtPoint.Z + newMesh.CenterPoint.Z
                        );
                }
            }

            return newMesh;
        }

        public Matrix3D GetParentedTransformMatrixAtFrame(int frame)
        {
            return GetParentedTransformMatrixAtFrame(frame, true, true);
        }
        public Matrix3D GetParentedTransformMatrixAtFrame(int frame, bool withSelf)
        {
            return GetParentedTransformMatrixAtFrame(frame, withSelf, true);
        }
        public Matrix3D GetParentedTransformMatrixAtFrame(int frame, bool withSelf, bool cachedParent)
        {
            // Get our own transform matrix
            Matrix3D mTr = (withSelf) ? _currentLayerMatrix : new Matrix3D();

            // Multiply with possible parent transform matrixes
            CompLayerBase parentLayer = ParentLayer;
            while (parentLayer != null)
            {
                //mTrFinal = Matrix3D.Multiply(mTr, parentLayer.GetTransformMatrixAtFrame(frame));
                if (cachedParent)
                    mTr = Matrix3D.Multiply(mTr, parentLayer.CurrentLayerMatrix);
                else
                    mTr = Matrix3D.Multiply(mTr, parentLayer.GetTransformMatrixAtFrame(frame));
                parentLayer = parentLayer.ParentLayer;
            }

            return mTr;
        }

        public LayerData DataStructure()
        {
            Debug.WriteLine("Creating Layer Datastructure");

            LayerData layerData = new LayerData()
            {
                LayerIndex = LayerIndex,
                UniqueID = UniqueID,
                LayerType = LayerType,
                LayerName = LayerName,
                ParentLayerIndex = ((ParentLayer == null) ? -1 : ParentLayer.LayerIndex),
                IsCollapsed = IsCollapsed,
                IsSelected = IsSelected,
                IsActiveCamera = IsActiveCamera,
                NumProperties = Properties.Count,
                Properties = new PropertyDataBase[Properties.Count]
            };

            // Populate properties
            for (int a = 0; a < Properties.Count; a++)
            {
                layerData.Properties[a] = Properties[a].DataStructure();
            }

            return layerData;
        }
    }
}
